#ifndef NIGHT_TENSOR_OPS
#define NIGHT_TENSOR_OPS

#include <functional>

#include "core.hpp"
#include "shape.hpp"
#include "expr.hpp"
#include "composite.hpp"

namespace night {

namespace tensor {

template< typename E, typename R, typename S >
inline reference_tensor< by_cref<E>, R, S > eval ( Base<E,R,S> const & src ) { return derived<E> (src); }

template< typename E >
struct scalar_expr {
	typedef typename detail::remove_cref<typename E::result_type>::type value_type;
	typedef value_type const & result_type;
	typedef value_type const & param_type;
	typedef shape<> shape_type;
	typedef scalar_view< by_cref<value_type> > type;
};

#define MAKE_SCALAR_BY_EXPR_OP(TENSOR,NAME,OP,BASE)                                                                        \
  template< typename RE, typename RR, typename RS >                                                                        \
  inline TENSOR< NAME,                                                                                                     \
    by_val< typename scalar_expr<RE>::type >, typename scalar_expr<RE>::result_type, typename scalar_expr<RE>::shape_type, \
    by_cref<RE>, RR, RS                                                                                                    \
  > operator OP ( typename scalar_expr<RE>::param_type lhs, BASE<RE,RR,RS> const & rhs ) {                                 \
    return TENSOR< NAME,                                                                                                   \
      by_val<typename scalar_expr<RE>::type>, typename scalar_expr<RE>::result_type, typename scalar_expr<RE>::shape_type, \
      by_cref<RE>, RR, RS                                                                                                  \
    > ( lhs, derived<RE> (rhs) );                                                                                          \
  }

#define MAKE_EXPR_BY_SCALAR_OP(TENSOR,NAME,OP,BASE)                                                                        \
  template< typename LE, typename LR, typename LS >                                                                        \
  inline TENSOR< NAME,                                                                                                     \
    by_cref<LE>, LR, LS,                                                                                                   \
    by_val< typename scalar_expr<LE>::type >, typename scalar_expr<LE>::result_type, typename scalar_expr<LE>::shape_type  \
  > operator OP ( BASE<LE,LR,LS> const & lhs, typename scalar_expr<LE>::param_type rhs ) {                                 \
    return TENSOR< NAME,                                                                                                   \
      by_cref<LE>, LR, LS,                                                                                                 \
      by_val<typename scalar_expr<LE>::type>, typename scalar_expr<LE>::result_type, typename scalar_expr<LE>::shape_type  \
    > ( derived<LE> (lhs), rhs );                                                                                          \
  }

#define MAKE_EXPR_BY_EXPR_OP(TENSOR,NAME,OP,BASE)                                                             \
  template< typename LE, typename LR, typename LS, typename RE, typename RR, typename RS >                    \
  inline TENSOR< NAME, by_cref<LE>, LR, LS, by_cref<RE>, RR, RS >                                             \
  operator OP ( BASE<LE,LR,LS> const & lhs, BASE<RE,RR,RS> const & rhs ) {                                    \
    return TENSOR< NAME, by_cref<LE>, LR, LS, by_cref<RE>, RR, RS > ( derived<LE> (lhs), derived<RE> (rhs) ); \
  }

#define MAKE_BINARY_OP(TENSOR,NAME,OP,BASE)   \
  MAKE_EXPR_BY_EXPR_OP(TENSOR,NAME,OP,BASE)   \
  MAKE_SCALAR_BY_EXPR_OP(TENSOR,NAME,OP,BASE) \
  MAKE_EXPR_BY_SCALAR_OP(TENSOR,NAME,OP,BASE) 

MAKE_BINARY_OP(composite_tensor,std::multiplies,*,Expr)
MAKE_BINARY_OP(binary_tensor,std::plus,+,Base)
MAKE_BINARY_OP(binary_tensor,std::minus,-,Base)

#undef MAKE_BINARY_OP

template< typename RE, typename RR, typename RS >
inline unary_tensor< std::negate, by_cref<RE>, RR, RS > operator - ( Expr<RE,RR,RS> const & rhs ) {
	return unary_tensor< std::negate, by_cref<RE>, RR, RS > ( derived<RE> (rhs) );
}

template< typename LE, typename RE >
struct IsEqual {
	bool & equal;
	typename LE::value_type lhs;
	typename RE::value_type rhs;

	IsEqual ( bool & src, typename LE::param_type x, typename RE::param_type y ) : equal (src), lhs (x), rhs (y) {}

	template< typename Shape >
	void operator () ( Shape const & s ) const { if ( this->equal ) this->equal = ( this->lhs (s) == this->rhs (s) ); }
};
	
template< typename LE, typename LR, typename LS, typename RE, typename RR, typename RS >
inline bool operator == ( Base<LE,LR,LS> const & lhs, Base<RE,RR,RS> const & rhs ) {
	bool equal = true;
	for_each<LS>::apply( shape<> (), IsEqual< by_cref<LE>, by_cref<RE> > ( equal, derived<LE> (lhs), derived<RE> (rhs) ) );
	return equal;
}

} // end namespace night::tensor

} // end namespace nigth

#endif
